package org.kolonitsky.css;

import org.jetbrains.annotations.NotNull;
import org.kolonitsky.css.writers.SelectorsXmlWriter;
import org.w3c.css.sac.*;
import static org.w3c.css.sac.LexicalUnit.*;

import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

/**
 * @author Alex.Kolonitsky
 */
public class CssToXmlDocHandler implements DocumentHandler {

    protected final XMLStreamWriter writer;
    protected final SelectorsXmlWriter selectorWriter;

    public CssToXmlDocHandler(@NotNull Writer outputFile) {
        XMLOutputFactory output = XMLOutputFactory.newInstance();
        try {
            writer = output.createXMLStreamWriter(outputFile);
            writer.writeStartDocument();

            selectorWriter = new SelectorsXmlWriter(writer);
        } catch (XMLStreamException e) {
            throw new RuntimeException("", e);
        }
    }

    public CssToXmlDocHandler(String fileName) throws IOException {
        this(new FileWriter(fileName + ".xml"));
    }

    public void flush() {
        try {
            writer.writeEndDocument();
            writer.flush();
        } catch (XMLStreamException e) {
            e.printStackTrace();  // TODO
        }
    }

    public void startDocument(InputSource source) throws CSSException {
        try {
            writer.writeStartElement("doc");
        } catch (XMLStreamException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    public void endDocument(InputSource source) throws CSSException {
        try {
            writer.writeEndElement();
        } catch (XMLStreamException e) {
            e.printStackTrace();
        }
    }

    public void comment(String text) throws CSSException {
        try {
            writer.writeEmptyElement("comment");
            writer.writeAttribute("val", text);
        } catch (XMLStreamException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    public void ignorableAtRule(String atRule) throws CSSException {
        System.out.println(".ignorableAtRule");
    }

    public void namespaceDeclaration(String prefix, String uri) throws CSSException {
        System.out.println(".namespaceDeclaration");
    }

    public void importStyle(String uri, SACMediaList media, String defaultNamespaceURI) throws CSSException {
        System.out.println(".importStyle");
    }

    public void startMedia(SACMediaList media) throws CSSException {
        try {
            writer.writeStartElement("media");

            StringBuilder devices = new StringBuilder();
            for (int i = 0; i < media.getLength(); i++) {
                devices.append(media.item(i));
            }
            writer.writeAttribute("devices", devices.toString());

        } catch (XMLStreamException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    public void endMedia(SACMediaList media) throws CSSException {
        try {
            writer.writeEndElement();
        } catch (XMLStreamException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    public void startPage(String name, String pseudo_page) throws CSSException {
        try {
            writer.writeStartElement("page");
            writer.writeAttribute("name", name);
            writer.writeAttribute("pseudo_page", pseudo_page);
        } catch (XMLStreamException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    public void endPage(String name, String pseudo_page) throws CSSException {
        try {
            writer.writeEndElement();
        } catch (XMLStreamException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    public void startFontFace() throws CSSException {
        System.out.println(".startFontFace");
    }

    public void endFontFace() throws CSSException {
        System.out.println(".endFontFace");
    }

    public void startSelector(SelectorList selectors) throws CSSException {
        try {
            writer.writeStartElement("rule");
            for (int i = 0; i < selectors.getLength(); i++) {
                selectorWriter.writeSelector_(selectors.item(i));
            }
        } catch (XMLStreamException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    public void endSelector(SelectorList selectors) throws CSSException {
        try {
            writer.writeEndElement();
        } catch (XMLStreamException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    public void property(String name, LexicalUnit value, boolean important) throws CSSException {
        try {
            startProperty();
            writer.writeAttribute("name", name);
            try {
                writer.writeAttribute("value", getAllLexicalValue(value));
            } catch (IllegalStateException e) {
                e.printStackTrace();  // TODO
            }
            if (important) {
                writer.writeAttribute("important", "true");
            }

        } catch (XMLStreamException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    protected void startProperty() throws XMLStreamException {
        writer.writeEmptyElement("property");
    }

    private String writeLexicalValue(LexicalUnit value) {

        switch (value.getLexicalUnitType()) {
            case SAC_OPERATOR_COMMA :
                return ",";

            case SAC_OPERATOR_PLUS :
                return "+";

            case SAC_OPERATOR_MINUS :
                return "-";

            case SAC_OPERATOR_MULTIPLY :
                return "*";

            case SAC_OPERATOR_SLASH :
                return "/";

            case SAC_OPERATOR_MOD :
                return "%";

            case SAC_OPERATOR_EXP :
                return "^";

            case SAC_OPERATOR_LT :
                return "<";

            case SAC_OPERATOR_GT :
                return ">";

            case SAC_OPERATOR_LE :
                return "<=";

            case SAC_OPERATOR_GE :
                return ">=";

            case SAC_OPERATOR_TILDE :
                return "~";

            case SAC_INHERIT :
                return "inherit";

            case SAC_INTEGER :
                return "" + value.getIntegerValue();

            case SAC_REAL :
            case SAC_EM :
            case SAC_EX :
            case SAC_PIXEL :
            case SAC_INCH :
            case SAC_CENTIMETER :
            case SAC_MILLIMETER :
            case SAC_POINT :
            case SAC_PICA :
            case SAC_PERCENTAGE :
                return value.getFloatValue() + value.getDimensionUnitText();

            case SAC_URI :
                return value.getStringValue();

            case SAC_COUNTER_FUNCTION :
            case SAC_COUNTERS_FUNCTION :
                return value.getFunctionName() + "(" + writeLexicalValue(value.getParameters()) + ")";
            case SAC_RGBCOLOR :
                return getParameters(value);

            case SAC_DEGREE :
            case SAC_GRADIAN :
            case SAC_RADIAN :
            case SAC_MILLISECOND :
            case SAC_SECOND :
            case SAC_HERTZ :
            case SAC_KILOHERTZ :
            case SAC_DIMENSION :
                return value.getFloatValue() + value.getDimensionUnitText();

            case SAC_STRING_VALUE :
                return "'" + value.getStringValue() + "'";

            case SAC_IDENT :
            case SAC_ATTR :
                return value.getStringValue();

            case SAC_FUNCTION :
            case SAC_RECT_FUNCTION :
                return getParameters(value);

            case SAC_UNICODERANGE :
                return "A unicode range. @@TO BE DEFINED";

            case SAC_SUB_EXPRESSION :
                return writeLexicalValue(value.getSubValues());

            default:
                return "!!null";
        }
    }

    private String getParameters(LexicalUnit value){
        String result =  value.getFunctionName() + "(" + writeLexicalValue(value.getParameters()) ;
        if(value.getParameters().getNextLexicalUnit()!= null){
            result += " "+ getAllLexicalValue(value.getParameters().getNextLexicalUnit());
        }
        return result + ")";
    }

    private String getAllLexicalValue(LexicalUnit value) {
        String result = writeLexicalValue(value);
        if(value.getNextLexicalUnit()!= null){
            result += " "+ getAllLexicalValue(value.getNextLexicalUnit());
        }
        return result;
    }
}
